﻿using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using DigestChecker.Cryptography;
using ThreadState = System.Threading.ThreadState;

//using ThreadState = System.Threading.ThreadState;

namespace DigestChecker
{
    internal class Digest : IDisposable
    {
        private string _fileName;
        private string _hash;
        private Thread _computeThread;
        private FileStream _fileStream;
        private AlgorithmType _algorithmType;

        public Digest(string fileName)
        {
            _fileName = fileName;

        }

        public Digest(string fileName, AlgorithmType algorithmType) : this(fileName)
        {
            _algorithmType = algorithmType;

        }

        public bool IsComplete 
        { 
            get { return _computeThread.ThreadState == ThreadState.Stopped; }
        }

        public string Hash
        {
            get
            {
                return _hash;
            }
        }

        public void ComputeHash()
        {
            _computeThread = new Thread(new ThreadStart(Compute)) {IsBackground = true};
            _computeThread.Start();
        }

        public void Compute()
        {
            var digest = GetHashAlgorithm();

            byte[] data = new byte[0];
            var file = ReadFile();

            if (file.GetType() == typeof (Byte[]))
            {
                byte[] binaryFile = (byte[])file;
                data = digest.ComputeHash(binaryFile);
            }
            else if(file.GetType() == typeof(FileStream))
            {
                var fs = (FileStream) file;
                data = digest.ComputeHash(fs);
            }

            //digest.ComputeHash(reader.ReadBytes((int)fs.Length));
            
            // Create a new Stringbuilder to collect the bytes
            // and create a string.
            StringBuilder sBuilder = new StringBuilder();

            // Loop through each byte of the hashed data 
            // and format each one as a hexadecimal string.
            for (int i = 0; i < data.Length; i++)
            {
                sBuilder.Append(data[i].ToString("x2", CultureInfo.InvariantCulture));
            }

            // Return the hexadecimal string.
            _hash = sBuilder.ToString();
            //_hash = Convert.ToBase64String(digest.Hash);
        }

        private HashAlgorithm GetHashAlgorithm()
        {
            HashAlgorithm digest;
            switch (_algorithmType)
            {
                case AlgorithmType.Sha1:

                    SHA1 digestSha1 = new SHA1CryptoServiceProvider();
                    digest = digestSha1;
                    break;

                case AlgorithmType.Md5:
                    MD5 digestMd5 = new MD5CryptoServiceProvider();
                    digest = digestMd5;
                    break;

                case AlgorithmType.Crc:
                    Crc32 digestCrc32 = new Crc32CryptoServiceProvider();
                    digest = digestCrc32;
                    break;

                default:
                    MD5 digestDefault = new MD5CryptoServiceProvider();
                    digest = digestDefault;
                    break;
            }
            return digest;
        }

        private Object ReadFile()
        {
            try
            {

                _fileStream = new FileStream(_fileName, FileMode.Open, FileAccess.Read);

                if (_fileStream.Length <= 2147483647)
                {
                    return File.ReadAllBytes(_fileName);
                }

                return _fileStream;

            }
            catch (FileNotFoundException ioEx)
            {
                Debug.WriteLine(ioEx.Message);
                if (_fileStream != null)
                {
                    _fileStream.Close();
                }
            }
            return null;
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }


        // NOTE: Leave out the finalizer altogether if this class doesn't 
        // own unmanaged resources itself, but leave the other methods
        // exactly as they are. 
        ~Digest() 
        {
            // Finalizer calls Dispose(false)
            Dispose(false);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // free managed resources
                if (_fileStream != null)
                {
                    _fileStream.Close();
                    _fileStream = null;
                }
            }

            // free native resources if there are any.
        }
    }
}
